<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Tools;

use Srv\Libs\Common\CommFile;

class SslParse
{
    private bool $parseResult               = false;
    // 颁发对象
    private string $subjectC                = '';
    private string $subjectST               = '';
    private string $subjectL                = '';
    private string $subjectO                = '';   // 组织
    private string $subjectOU               = '';   // 组织单位
    private string $subjectCN               = '';   // 公用名
    // 颁发者
    private string $issuerC                 = '';
    private string $issuerO                 = '';   // 组织
    private string $issuerOU                = '';   // 组织单位
    private string $issuerCN                = '';   // 公用名
    // 有效期
    private int $validFromTime              = 0;    // 颁发日期
    private int $validToTime                = 0;    // 截止日期
    // 信息
    private string $signatureTypeSN         = '';   // 签名算法SN
    private string $signatureTypeLN         = '';   // 签名算法LN
    private string $version                 = '';   // 版本
    private string $hash                    = '';   // Hash
    private string $serialNumber            = '';   // 序号
    private string $serialNumberHex         = '';   // 序号十六进制
    private array $dnsList                  = [];   // DNS
    // 扩展
    private string $authorityKeyIdentifier  = '';
    private string $subjectKeyIdentifier    = '';
    private string $ocspUrl                 = '';
    private string $caUrl                   = '';

    /**
     * @param string $certFilePath
     * @return static
     * fromFile
     */
    public static function fromFile(string $certFilePath):self
    {
        $certStr    = '';
        CommFile::getContent($certFilePath, $certStr);
        return self::fromStr($certStr);
    }

    /**
     * @param string $certStr
     * @return static
     * fromStr
     */
    public static function fromStr(string $certStr):self
    {
        $__class__      = static::class;
        $tagObject      = new $__class__();
        $tagObject->reset();
        $certInfo       = openssl_x509_parse($certStr, true);
        if(is_array($certInfo) && isset($certInfo['subject'])) $tagObject->parse($certInfo);
        return $tagObject;
    }

    /**
     * @param string $hostName
     * @param int $timeout
     * @return static
     * fromHostName
     */
    public static function fromHostName(string $hostName, int $timeout = 3):self
    {
        $__class__      = static::class;
        $tagObject      = new $__class__();
        $tagObject->reset();
        $context        = stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true, 'capture_peer_cert' => true]]);
        $timeout        = min(max($timeout, 1), 30);
        $errno          = 0;
        $errstr         = '';
        $stream         = stream_socket_client('ssl://'.$hostName.':443', $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context);
        if($stream === false) return $tagObject;
        $content        = stream_context_get_params($stream);
        fclose($stream);
        $certificate    = $content['options']['ssl']['peer_certificate']??false;
        if($certificate !== false){
            $certInfo   = openssl_x509_parse($certificate, true);
            if(is_array($certInfo) && isset($certInfo['subject'])) $tagObject->parse($certInfo);
        }
        return $tagObject;
    }

    /**
     * @param array $certInfo
     * @return void
     * parse
     */
    private function parse(array $certInfo):void
    {
        if(!isset($certInfo['subject'])){
            $this->parseResult          = false;
            return;
        }
        $this->parseResult              = true;
        // 颁发对象
        $this->subjectC                 = strval($certInfo['subject']['C']??'');
        $this->subjectST                = strval($certInfo['subject']['ST']??'');
        $this->subjectL                 = strval($certInfo['subject']['L']??'');
        $this->subjectO                 = strval($certInfo['subject']['O']??'');                // 组织
        $this->subjectOU                = strval($certInfo['subject']['OU']??'');               // 组织单位
        $this->subjectCN                = strtoupper(strval($certInfo['subject']['CN']??''));   // 公用名
        // 颁发者
        $this->issuerC                  = strval($certInfo['issuer']['C']??'');
        $this->issuerO                  = strval($certInfo['issuer']['O']??'');                 // 组织
        $this->issuerOU                 = strval($certInfo['issuer']['OU']??'');                // 组织单位
        $this->issuerCN                 = strtoupper(strval($certInfo['issuer']['CN']??''));    // 公用名
        // 有效期
        $this->validFromTime            = intval($certInfo['validFrom_time_t']??0);             // 颁发日期
        $this->validToTime              = intval($certInfo['validTo_time_t']??0);               // 截止日期
        // 信息
        $this->signatureTypeSN          = strval($certInfo['signatureTypeSN']??'');             // 签名算法SN
        $this->signatureTypeLN          = strval($certInfo['signatureTypeLN']??'');             // 签名算法LN
        $this->version                  = strval($certInfo['version']??'');                     // 版本
        $this->hash                     = strtoupper(strval($certInfo['hash']??''));            // Hash
        $this->serialNumber             = strval($certInfo['serialNumber']??'');                // 序号
        $this->serialNumberHex          = strtoupper(strval($certInfo['serialNumberHex']??'')); // 序号十六进制
        // DNS
        $subjectAltName                 = strval($certInfo['extensions']['subjectAltName']??'');
        $this->dnsList                  = array_map(function($value){ return trim(preg_replace('/dns:/i', '', strtolower($value))); }, explode(',', $subjectAltName));
        // 扩展
        $this->authorityKeyIdentifier   = preg_replace('/^KEYID:/', '', strtoupper(strval($certInfo['extensions']['authorityKeyIdentifier']??'')));
        $this->subjectKeyIdentifier     = strtoupper(strval($certInfo['extensions']['subjectKeyIdentifier']??''));
        $authorityInfoAccess            = strval($certInfo['extensions']['authorityInfoAccess']??'');
        $this->ocspUrl                  = '';
        $this->caUrl                    = '';
        if(strlen($authorityInfoAccess) > 0) foreach (explode("\n", $authorityInfoAccess) as $tmpVal){
            $tmpVal                     = trim($tmpVal);
            if(preg_match('/^(.*)\s+-\s+URI:(\S+)/', $tmpVal, $tmpMatch)){
                if(strpos($tmpMatch[1], 'CA') !== false) $this->caUrl = $tmpMatch[2];
                if(strpos($tmpMatch[1], 'OCSP') !== false) $this->ocspUrl = $tmpMatch[2];
            }
        }
    }

    /**
     * @return void
     * reset
     */
    private function reset():void
    {
        $this->parseResult              = false;
        $this->subjectC                 = '';
        $this->subjectST                = '';
        $this->subjectL                 = '';
        $this->subjectO                 = '';
        $this->subjectOU                = '';
        $this->subjectCN                = '';
        $this->issuerC                  = '';
        $this->issuerO                  = '';
        $this->issuerOU                 = '';
        $this->issuerCN                 = '';
        $this->validFromTime            = 0;
        $this->validToTime              = 0;
        $this->signatureTypeSN          = '';
        $this->signatureTypeLN          = '';
        $this->version                  = '';
        $this->hash                     = '';
        $this->serialNumber             = '';
        $this->serialNumberHex          = '';
        $this->dnsList                  = [];
        $this->authorityKeyIdentifier   = '';
        $this->subjectKeyIdentifier     = '';
        $this->ocspUrl                  = '';
        $this->caUrl                    = '';
    }

    /**
     * @return array
     * getParseData
     */
    public function getParseData():array
    {
        return [
            'parseResult'               => $this->parseResult,
            'subjectC'                  => $this->subjectC,
            'subjectST'                 => $this->subjectST,
            'subjectL'                  => $this->subjectL,
            'subjectO'                  => $this->subjectO,
            'subjectOU'                 => $this->subjectOU,
            'subjectCN'                 => $this->subjectCN,
            'issuerC'                   => $this->issuerC,
            'issuerO'                   => $this->issuerO,
            'issuerOU'                  => $this->issuerOU,
            'issuerCN'                  => $this->issuerCN,
            'validFromTime'             => $this->validFromTime,
            'validToTime'               => $this->validToTime,
            'signatureTypeSN'           => $this->signatureTypeSN,
            'signatureTypeLN'           => $this->signatureTypeLN,
            'version'                   => $this->version,
            'hash'                      => $this->hash,
            'serialNumber'              => $this->serialNumber,
            'serialNumberHex'           => $this->serialNumberHex,
            'dnsList'                   => $this->dnsList,
            'authorityKeyIdentifier'    => $this->authorityKeyIdentifier,
            'subjectKeyIdentifier'      => $this->subjectKeyIdentifier,
            'ocspUrl'                   => $this->ocspUrl,
            'caUrl'                     => $this->caUrl,
        ];
    }

    /**
     * @return bool
     */
    public function isParseResult(): bool
    {
        return $this->parseResult;
    }

    /**
     * @return string
     */
    public function getSubjectC(): string
    {
        return $this->subjectC;
    }

    /**
     * @return string
     */
    public function getSubjectST(): string
    {
        return $this->subjectST;
    }

    /**
     * @return string
     */
    public function getSubjectL(): string
    {
        return $this->subjectL;
    }

    /**
     * @return string
     */
    public function getSubjectO(): string
    {
        return $this->subjectO;
    }

    /**
     * @return string
     */
    public function getSubjectOU(): string
    {
        return $this->subjectOU;
    }

    /**
     * @return string
     */
    public function getSubjectCN(): string
    {
        return $this->subjectCN;
    }

    /**
     * @return string
     */
    public function getIssuerC(): string
    {
        return $this->issuerC;
    }

    /**
     * @return string
     */
    public function getIssuerO(): string
    {
        return $this->issuerO;
    }

    /**
     * @return string
     */
    public function getIssuerOU(): string
    {
        return $this->issuerOU;
    }

    /**
     * @return string
     */
    public function getIssuerCN(): string
    {
        return $this->issuerCN;
    }

    /**
     * @return int
     */
    public function getValidFromTime(): int
    {
        return $this->validFromTime;
    }

    /**
     * @return int
     */
    public function getValidToTime(): int
    {
        return $this->validToTime;
    }

    /**
     * @return string
     */
    public function getSignatureTypeSN(): string
    {
        return $this->signatureTypeSN;
    }

    /**
     * @return string
     */
    public function getSignatureTypeLN(): string
    {
        return $this->signatureTypeLN;
    }

    /**
     * @return string
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @return string
     */
    public function getHash(): string
    {
        return $this->hash;
    }

    /**
     * @return string
     */
    public function getSerialNumber(): string
    {
        return $this->serialNumber;
    }

    /**
     * @return string
     */
    public function getSerialNumberHex(): string
    {
        return $this->serialNumberHex;
    }

    /**
     * @return array
     */
    public function getDnsList(): array
    {
        return $this->dnsList;
    }

    /**
     * @return string
     */
    public function getAuthorityKeyIdentifier(): string
    {
        return $this->authorityKeyIdentifier;
    }

    /**
     * @return string
     */
    public function getSubjectKeyIdentifier(): string
    {
        return $this->subjectKeyIdentifier;
    }

    /**
     * @return string
     */
    public function getOcspUrl(): string
    {
        return $this->ocspUrl;
    }

    /**
     * @return string
     */
    public function getCaUrl(): string
    {
        return $this->caUrl;
    }
}